﻿using SoldiHive.Core.Cache;
using SoldiHive.Core.NodeJs;
using SoldiHive.Core.Parsing;
using SoldiHive.Core.Persistence;
using SoldiHive.Core.Ratings;
using SoldiHive.Model;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;

namespace SoldiHive.Core.Adapters
{
	public class NasdaqAdapter : BaseAdapter, IEarningsStatementsAdapter
	{
		private const string GET_PRICE_TARGET_SCRIPT = "nasdaq_getpricetarget.js";
		private const string GET_PAST_EPS_DATES_SCRIPT = "nasdaq_getpastearningsdates.js";
		private const string GET_FUTURE_EPS_DATES_SCRIPT = "nasdaq_getfuturearningsdates.js";
		private const string GET_EPS_DATE_SCRIPT = "nasdaq_getearningsdate.js";

		private ICompanyRepository companyRepository;

		public override Site Source
		{
			get 
			{
				return Site.Nasdaq;
			}
		}

		public NasdaqAdapter(ICompanyRepository companyRepository, IScriptExecutor scriptExecutor, IParser parser, IRatingStrategy ratingStrategy, ICache cache, IAdapterConfiguration configuration)
			: base(scriptExecutor, parser, ratingStrategy, cache, configuration)
		{
			this.companyRepository = companyRepository;
		}

		public override Rating GetRating(Company company)
		{
			var currentPrice = this.GetCurrentPrice(company);
			var priceTarget = this.GetPriceTarget(company);

			var analystOpinion = new AnalystOpinion()
			{
				Current = this.GetOpinion(company),
				PriceTarget = priceTarget
			};

			var rating = this.ratingStrategy.GetRating(currentPrice, analystOpinion);

			rating.Source = Site.Nasdaq;

			return rating;
		}

		public Opinion GetOpinion(Company company)
		{
			var cacheKey = this.cache.Key(this.Source, "GetOpinion", company.Ticker);
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<Opinion>(cacheKey);
			}

			var ticker = company.Ticker;
			var tickerUrl = "http://www.nasdaq.com/charts/{0}_smallrm.jpeg";
			tickerUrl = string.Format(tickerUrl, ticker);

			try
			{
				using (var client = new WebClient())
				{
					var mozillaUserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36";
					client.Headers.Add(HttpRequestHeader.UserAgent, mozillaUserAgent);

					using (var stream = client.OpenRead(tickerUrl))
					{
						var opinion = this.GetOpinion(stream);

						if (this.cache.Enabled)
						{
							this.cache.Put(cacheKey, opinion);
						}

						return opinion;
					}
				}
			}
			catch (WebException)
			{
				return Opinion.NA;
			}
		}

		public Opinion GetOpinion(Stream stream)
		{
			const double leftBound = 0.032;
			const double rightBound = 0.036;
			const double centerLine = 0.39;

			const double stretch = 1.1; // to stretch rate since NDQ rate never stick to the right or left

			var image = Image.FromStream(stream) as Bitmap;

			var left = (int)((double)image.Width * leftBound);
			var width = (int)((double)image.Width * (1 - leftBound - rightBound));
			var right = left + width;
			var y = (int)((double)image.Height * centerLine);

			var pixels = new List<Color>();
			for (int x = left; x < right; x++)
			{
				var pixel = image.GetPixel(x, y);
				pixels.Add(pixel);
			}

			var rgbSums = pixels.Select(pixel => (int)pixel.R + (int)pixel.G + (int)pixel.B).ToList();
			var maxRgbSum = rgbSums.Max();
			var maxIndex = rgbSums.IndexOf(maxRgbSum);

			var rate = Rating.MaxRate * Math.Round((double)maxIndex / (double)width, 1);
			rate = 0.5 * Rating.MaxRate + (rate - Rating.MaxRate * 0.5) * stretch;

			var opinion = this.parser.ParseOpinion(rate);

			return opinion;
		}

		private double? GetPriceTarget(Company company)
		{
			var result = this.scriptExecutor.Execute(NasdaqAdapter.GET_PRICE_TARGET_SCRIPT, company.Ticker);
			if (string.IsNullOrEmpty(result))
			{
				return null;
			}

			var priceTarget = this.parser.ParseDouble(result);

			return priceTarget;
		}

		public IList<EarningsStatement> GetPastEarningsStatements(Company company)
		{
			var result = this.scriptExecutor.Execute(NasdaqAdapter.GET_PAST_EPS_DATES_SCRIPT, company.Ticker);
			if (string.IsNullOrEmpty(result))
			{
				return Enumerable.Empty<EarningsStatement>().ToList();
			}

			var dates = this.parser.ParseDates(result);
			var pastEps = dates
				.Select(date => new EarningsStatement() 
				{
					Date = date,
					Company = company,
					Time = Time.NA
				})
				.ToList();

			return pastEps;
		}

		public IList<EarningsStatement> GetEarningsStatements(DateTime date)
		{
			var result = this.scriptExecutor.Execute(NasdaqAdapter.GET_FUTURE_EPS_DATES_SCRIPT, date.Year, date.Month, date.Day);
			if (string.IsNullOrEmpty(result))
			{
				return Enumerable.Empty<EarningsStatement>().ToList();
			}

			var futureEps = this.parser.ParseEarningsStatements(result);

			return futureEps;
		}

		public EarningsStatement GetEarningsStatement(Company company)
		{
			var result = this.scriptExecutor.Execute(NasdaqAdapter.GET_EPS_DATE_SCRIPT, company);
			if (string.IsNullOrEmpty(result))
			{
				return null;
			}

			var earningsStatementDate = this.parser.ParseDate(result);
			if (!earningsStatementDate.HasValue)
			{
				return null;
			}

			var earningsStatements = this.GetEarningsStatements(earningsStatementDate.Value);
			var earningsStatement = earningsStatements.SingleOrDefault(p => p.Company == company);

			return earningsStatement;
		}
	}
}
